/*
 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export function Assert(value: boolean, message?: string) {
    if (!value) Assert.fail(message)
}

export namespace Assert {
    /**
     * Throws a failure.
     * @param message - message to display on error
     */
    export function fail(message?: string): never {
        throw new Error(message ?? "assert")
    }

    /**
     * Asserts that object is truthy.
     *
     * T   Type of object.
     * @param object   Object to test.
     * @param message    Message to display on error.
     */
    export function isOk<T>(value: T, message?: string): void {
        Assert.fail("Assert.isOk unsupported")
    }

    /**
     * Asserts that object is truthy.
     *
     * T   Type of object.
     * @param object   Object to test.
     * @param message    Message to display on error.
     */
    export function ok<T>(value: T, message?: string): void {
        Assert.fail("Assert.ok unsupported")
    }

    /**
     * Asserts that object is falsy.
     *
     * T   Type of object.
     * @param object   Object to test.
     * @param message    Message to display on error.
     */
    export function isNotOk<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotOk unsupported")
    }

    /**
     * Asserts that object is falsy.
     *
     * T   Type of object.
     * @param object   Object to test.
     * @param message    Message to display on error.
     */
    export function notOk<T>(value: T, message?: string): void {
        Assert.fail("Assert.notOk unsupported")
    }

    /**
     * Asserts non-strict equality (==) of actual and expected.
     * @param actual - actual value
     * @param expected - potential expected value
     * @param message - message to display on error
     */
    export function equal<T>(actual: T, expected: T, message?: string): void {
        if (actual == expected) return
        Assert.fail(message ?? `actual '${actual}' is not equal to expected '${expected}'`)
    }

    /**
     * Asserts non-strict inequality (!=) of actual and expected.
     * @param actual - actual value
     * @param expected - potential expected value
     * @param message - message to display on error
     */
    export function notEqual<T>(actual: T, expected: T, message?: string): void {
        if (actual != expected) return
        Assert.fail(message ?? `actual '${actual}' is equal to expected '${expected}'`)
    }

    /**
     * Asserts strict equality (===) of actual and expected.
     * @param actual - actual value
     * @param expected - potential expected value
     * @param message - message to display on error
     */
    export function strictEqual<T>(actual: T, expected: T, message?: string): void {
        if (actual === expected) return
        Assert.fail(message ?? `actual '${actual}' is not strictly equal to expected '${expected}'`)
    }

    /**
     * Asserts strict inequality (!==) of actual and expected.
     * @param actual - actual value
     * @param expected - potential expected value
     * @param message - message to display on error
     */
    export function notStrictEqual<T>(actual: T, expected: T, message?: string): void {
        if (actual !== expected) return
        Assert.fail(message ?? `actual '${actual}' is strictly equal to expected '${expected}'`)
    }

    /**
     * Asserts that actual is deeply equal to expected.
     * @param actual - actual value array
     * @param expected - potential expected value array
     * @param message - message to display on error
     */
    export function deepEqual<T>(actual: Array<T>, expected: Array<T>, message?: string): void {
        if (equalArrayContent(actual, expected)) return
        Assert.fail(message ?? `actual '${actual}' is not deeply equal to expected '${expected}'`)
    }

    /**
     * Asserts that actual is not deeply equal to expected.
     * @param actual - actual value array
     * @param expected - potential expected value array
     * @param message - message to display on error
     */
    export function notDeepEqual<T>(actual: Array<T>, expected: Array<T>, message?: string): void {
        if (!equalArrayContent(actual, expected)) return
        Assert.fail(message ?? `actual '${actual}' is deeply equal to expected '${expected}'`)
    }

    /**
     * Asserts valueToCheck is strictly greater than (>) valueToBeAbove.
     *
     * @param valueToCheck   Actual value.
     * @param valueToBeAbove   Minimum Potential expected value.
     * @param message   Message to display on error.
     */
    export function isAbove(valueToCheck: number, valueToBeAbove: number, message?: string): void {
        Assert.fail("Assert.isAbove unsupported")
    }

    /**
     * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtLeast.
     *
     * @param valueToCheck   Actual value.
     * @param valueToBeAtLeast   Minimum Potential expected value.
     * @param message   Message to display on error.
     */
    export function isAtLeast(valueToCheck: number, valueToBeAtLeast: number, message?: string): void {
        Assert.fail("Assert.isAtLeast unsupported")
    }

    /**
     * Asserts valueToCheck is strictly less than (<) valueToBeBelow.
     *
     * @param valueToCheck   Actual value.
     * @param valueToBeBelow   Minimum Potential expected value.
     * @param message   Message to display on error.
     */
    export function isBelow(valueToCheck: number, valueToBeBelow: number, message?: string): void {
        Assert.fail("Assert.isBelow unsupported")
    }

    /**
     * Asserts valueToCheck is less than or equal to (<=) valueToBeAtMost.
     *
     * @param valueToCheck   Actual value.
     * @param valueToBeAtMost   Minimum Potential expected value.
     * @param message   Message to display on error.
     */
    export function isAtMost(valueToCheck: number, valueToBeAtMost: number, message?: string): void {
        Assert.fail("Assert.isAtMost unsupported")
    }

    /**
     * Asserts that value is true.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isTrue(value?: boolean, message?: string): void {
        if (value == true) return
        Assert.fail(message ?? `actual '${value}' is not true unexpectedly`)
    }

    /**
     * Asserts that value is not true.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isNotTrue(value?: boolean, message?: string): void {
        if (value != true) return
        Assert.fail(message ?? `actual '${value}' is true unexpectedly`)
    }

    /**
     * Asserts that value is false.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isFalse(value?: boolean, message?: string): void {
        if (value == false) return
        Assert.fail(message ?? `actual '${value}' is not false unexpectedly`)
    }

    /**
     * Asserts that value is not false.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isNotFalse(value?: boolean, message?: string): void {
        if (value != false) return
        Assert.fail(message ?? `actual '${value}' is false unexpectedly`)
    }

    /**
     * Asserts that value is null.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isNull<T>(value: T, message?: string): void {
        if (value == null) return // replace with '===' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' is not null unexpectedly`)
    }

    /**
     * Asserts that value is not null.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isNotNull<T>(value: T, message?: string): void {
        if (value != null) return // replace with '!==' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' is null unexpectedly`)
    }

    /**
     * Asserts that value is NaN.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNaN<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNaN unsupported")
    }

    /**
     * Asserts that value is not NaN.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotNaN<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotNaN unsupported")
    }

    /**
     * Asserts that the target is neither null nor undefined.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function exists<T>(value: T, message?: string): void {
        if (value == undefined || value == null) return // replace with '===' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' does not exist unexpectedly`)
    }

    /**
     * Asserts that the target is either null or undefined.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function notExists<T>(value: T, message?: string): void {
        if (value != undefined && value != null) return // replace with '!==' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' exists unexpectedly`)
    }

    /**
     * Asserts that value is undefined.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isUndefined<T>(value: T, message?: string): void {
        if (value == undefined) return // replace with '===' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' is defined unexpectedly`)
    }

    /**
     * Asserts that value is not undefined.
     * @param value - actual value
     * @param message - message to display on error
     */
    export function isDefined<T>(value: T, message?: string): void {
        if (value != undefined) return // replace with '!==' when panda-issue-19588 is fixed
        Assert.fail(message ?? `actual '${value}' is undefined unexpectedly`)
    }

    /**
     * Asserts that value is a function.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isFunction<T>(value: T, message?: string): void {
        Assert.fail("Assert.isFunction unsupported")
    }

    /**
     * Asserts that value is not a function.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotFunction<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotFunction unsupported")
    }

    /**
     * Asserts that value is an object of type 'Object'
     * (as revealed by Object.prototype.toString).
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     * @remarks The assertion does not match subclassed objects.
     */
    export function isObject<T>(value: T, message?: string): void {
        Assert.fail("Assert.isObject unsupported")
    }

    /**
     * Asserts that value is not an object of type 'Object'
     * (as revealed by Object.prototype.toString).
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotObject<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotObject unsupported")
    }

    /**
     * Asserts that value is an array.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isArray<T>(value: T, message?: string): void {
        Assert.fail("Assert.isArray unsupported")
    }

    /**
     * Asserts that value is not an array.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotArray<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotArray unsupported")
    }

    /**
     * Asserts that value is a string.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isString<T>(value: T, message?: string): void {
        Assert.fail("Assert.isString unsupported")
    }

    /**
     * Asserts that value is not a string.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotString<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotString unsupported")
    }

    /**
     * Asserts that value is a number.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNumber<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNumber unsupported")
    }

    /**
     * Asserts that value is not a number.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotNumber<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotNumber unsupported")
    }

    /**
     * Asserts that value is a finite number.
     * Unlike `.isNumber`, this will fail for `NaN` and `Infinity`.
     *
     * T   Type of value
     * @param value    Actual value
     * @param message   Message to display on error.
     */
    export function isFinite<T>(value: T, message?: string): void {
        Assert.fail("Assert.isFinite unsupported")
    }

    /**
     * Asserts that value is a boolean.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isBoolean<T>(value: T, message?: string): void {
        Assert.fail("Assert.isBoolean unsupported")
    }

    /**
     * Asserts that value is not a boolean.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param message   Message to display on error.
     */
    export function isNotBoolean<T>(value: T, message?: string): void {
        Assert.fail("Assert.isNotBoolean unsupported")
    }

    /**
     * Asserts that value's type is name, as determined by Object.prototype.toString.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param name   Potential expected type name of value.
     * @param message   Message to display on error.
     */
    export function typeOf<T>(value: T, name: string, message?: string): void {
        Assert.fail("Assert.typeOf unsupported")
    }

    /**
     * Asserts that value's type is not name, as determined by Object.prototype.toString.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param name   Potential expected type name of value.
     * @param message   Message to display on error.
     */
    export function notTypeOf<T>(value: T, name: string, message?: string): void {
        Assert.fail("Assert.notTypeOf unsupported")
    }

    /**
     * Asserts that value is an instance of constructor.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param construct   Potential expected contructor of value.
     * @param message   Message to display on error.
     * /
    static instanceOf<T>(value: T, construct: Function, message?: string): void*/

    /**
     * Asserts that value is not an instance of constructor.
     *
     * T   Type of value.
     * @param value   Actual value.
     * @param constructor   Potential expected contructor of value.
     * @param message   Message to display on error.
     * /
    static notInstanceOf<T>(value: T, type: Function, message?: string): void*/

    /**
     * Asserts that haystack includes needle.
     *
     * @param haystack   Container string.
     * @param needle   Potential substring of haystack.
     * @param message   Message to display on error.
     * /
    static include(haystack: string, needle: string, message?: string): void*/

    /**
     * Asserts that haystack includes needle.
     *
     * T   Type of values in haystack.
     * @param haystack   Container array, set or map.
     * @param needle   Potential value contained in haystack.
     * @param message   Message to display on error.
     * /
    static include<T>(
        haystack: ReadonlyArray<T> | ReadonlySet<T> | ReadonlyMap<any, T>,
        needle: T,
        message?: string,
    ): void;*/

    /**
     * Asserts that haystack includes needle.
     *
     * T   Type of values in haystack.
     * @param haystack   WeakSet container.
     * @param needle   Potential value contained in haystack.
     * @param message   Message to display on error.
     * /
    static include<T extends object>(haystack: WeakSet<T>, needle: T, message?: string): void;*/

    /**
     * Asserts that haystack includes needle.
     *
     * T   Type of haystack.
     * @param haystack   Object.
     * @param needle   Potential subset of the haystack's properties.
     * @param message   Message to display on error.
     * /
    static include<T>(haystack: T, needle: Partial<T>, message?: string): void;*/

    /**
     * Asserts that haystack does not includes needle.
     *
     * @param haystack   Container string.
     * @param needle   Potential substring of haystack.
     * @param message   Message to display on error.
     * /
    static notInclude(haystack: string, needle: string, message?: string): void;*/

    /**
     * Asserts that haystack does not includes needle.
     *
     * T   Type of values in haystack.
     * @param haystack   Container array, set or map.
     * @param needle   Potential value contained in haystack.
     * @param message   Message to display on error.
     * /
    static notInclude<T>(
        haystack: ReadonlyArray<T> | ReadonlySet<T> | ReadonlyMap<any, T>,
        needle: T,
        message?: string,
    ): void;*/

    /**
     * Asserts that haystack does not includes needle.
     *
     * T   Type of values in haystack.
     * @param haystack   WeakSet container.
     * @param needle   Potential value contained in haystack.
     * @param message   Message to display on error.
     * /
    static notInclude<T extends object>(haystack: WeakSet<T>, needle: T, message?: string): void;*/

    /**
     * Asserts that haystack does not includes needle.
     *
     * T   Type of haystack.
     * @param haystack   Object.
     * @param needle   Potential subset of the haystack's properties.
     * @param message   Message to display on error.
     * /
    static notInclude<T>(haystack: T, needle: Partial<T>, message?: string): void;*/

    /**
     * Asserts that value matches the regular expression regexp.
     *
     * @param value   Actual value.
     * @param regexp   Potential match of value.
     * @param message   Message to display on error.
     */
    export function match(value: string, regexp: RegExp, message?: string): void {
        Assert.fail("Assert.match unsupported")
    }

    /**
     * Asserts that value does not match the regular expression regexp.
     *
     * @param value   Actual value.
     * @param regexp   Potential match of value.
     * @param message   Message to display on error.
     */
    export function notMatch(expected: string, regexp: RegExp, message?: string): void {
        Assert.fail("Assert.notMatch unsupported")
    }

    /**
     * Asserts that fn will throw an error.
     *
     * @param fn   Function that may throw.
     * @param errMsgMatcher   Expected error message matcher.
     * @param ignored   Ignored parameter.
     * @param message   Message to display on error.
     * /
    static throw(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/

    /**
     * Asserts that the given function will throw an error.
     * @param func - a function that may throw an error
     * /
    static throw(func: () => void): void {
        Assert.throws(func)
    }

    /**
     * Asserts that the given function will throw an error.
     * @param func - a function that may throw an error
     */
    export function throws(func: () => void): void {
        let expected: Error | undefined = undefined
        try {
            func()
        } catch (error) {
            expected = error as Error
        }
        if (expected) return
        Assert.fail("expected error is not thrown")
    }

    /**
     * Asserts that fn will throw an error.
     *
     * @param fn   Function that may throw.
     * @param errorLike   Expected error constructor or error instance.
     * @param errMsgMatcher   Expected error message matcher.
     * @param message   Message to display on error.
     * /
    static throws(
        fn: () => void,
        errorLike?: ErrorConstructor | Error | null,
        errMsgMatcher?: RegExp | string | null,
        message?: string,
    ): void;*/

    /**
     * Asserts that fn will throw an error.
     *
     * @param fn   Function that may throw.
     * @param errMsgMatcher   Expected error message matcher.
     * @param ignored   Ignored parameter.
     * @param message   Message to display on error.
     * /
    static Throw(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/

    /**
     * Asserts that fn will throw an error.
     *
     * @param fn   Function that may throw.
     * @param errorLike   Expected error constructor or error instance.
     * @param errMsgMatcher   Expected error message matcher.
     * @param message   Message to display on error.
     * /
    static Throw(
        fn: () => void,
        errorLike?: ErrorConstructor | Error | null,
        errMsgMatcher?: RegExp | string | null,
        message?: string,
    ): void;*/

    /**
     * Asserts that fn will not throw an error.
     *
     * @param fn   Function that may throw.
     * @param errMsgMatcher   Expected error message matcher.
     * @param ignored   Ignored parameter.
     * @param message   Message to display on error.
     * /
    static doesNotThrow(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string): void;*/

    /**
     * Asserts that fn will not throw an error.
     *
     * @param fn   Function that may throw.
     * @param errorLike   Expected error constructor or error instance.
     * @param errMsgMatcher   Expected error message matcher.
     * @param message   Message to display on error.
     * /
    static doesNotThrow(
        fn: () => void,
        errorLike?: ErrorConstructor | Error | null,
        errMsgMatcher?: RegExp | string | null,
        message?: string,
    ): void;*/

    /**
     * Compares two values using operator.
     *
     * @param val1   Left value during comparison.
     * @param operator   Comparison operator.
     * @param val2   Right value during comparison.
     * @param message   Message to display on error.
     * /
    static operator(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string): void;*/

    /**
     * Asserts that the target is equal to expected, to within a +/- delta range.
     *
     * @param actual   Actual value
     * @param expected   Potential expected value.
     * @param delta   Maximum differenced between values.
     * @param message   Message to display on error.
     * /
    static closeTo(actual: number, expected: number, delta: number, message?: string): void;*/

    /**
     * Asserts that the target is equal to expected, to within a +/- delta range.
     *
     * @param actual   Actual value
     * @param expected   Potential expected value.
     * @param delta   Maximum differenced between values.
     * @param message   Message to display on error.
     * /
    static approximately(act: number, exp: number, delta: number, message?: string): void;*/

    /**
     * Asserts that non-object, non-array value inList appears in the flat array list.
     *
     * T   Type of list values.
     * @param inList   Value expected to be in the list.
     * @param list   List of values.
     * @param message   Message to display on error.
     */
    export function oneOf<T>(inList: T, list: T[], message?: string): void {
        Assert.fail("Assert.oneOf unsupported")
    }

    /**
     * Asserts that the target does not contain any values. For arrays and
     * strings, it checks the length property. For Map and Set instances, it
     * checks the size property. For non-function objects, it gets the count
     * of own enumerable string keys.
     *
     * T   Type of object
     * @param object   Actual value.
     * @param message   Message to display on error.
     */
    export function isEmpty<T>(object: T, message?: string): void {
        const size = getContainerSize(object)
        if (size != 0) Assert.fail(message ?? (size < 0 ? "unsupported container" : "container is not empty unexpectedly"))
    }

    /**
     * Asserts that the target contains values. For arrays and strings, it checks
     * the length property. For Map and Set instances, it checks the size property.
     * For non-function objects, it gets the count of own enumerable string keys.
     *
     * T   Type of object.
     * @param object   Object to test.
     * @param message    Message to display on error.
     */
    export function isNotEmpty<T>(object: T, message?: string): void {
        const size = getContainerSize(object)
        if (size <= 0) Assert.fail(message ?? (size < 0 ? "unsupported container" : "container is empty unexpectedly"))
    }
}

function equalArrayContent<T>(actual: Array<T>, expected: Array<T>): boolean {
    const length = actual.length
    if (expected.length != length) return false
    for (let i = 0; i < length; i++) {
        if (expected[i] != actual[i]) return false
    }
    return true
}

/**
 * @param object - a container to check
 * @returns a container size, or -1 if the given container is not supported
 */
function getContainerSize<T>(container: T): number {
    if (container instanceof Map) return container.size
    if (container instanceof Set) return container.size
    if (container instanceof Array) return container.length
    if (container instanceof String) return container.length
    return -1
}
